package sk.uniba.fmph.asdf.gui.frames;

import com.alee.extended.filechooser.SelectionMode;
import com.alee.extended.filechooser.WebFileChooser;
import com.alee.laf.GlobalConstants;
import com.alee.laf.StyleConstants;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import javax.swing.JOptionPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeSelectionModel;
import sk.uniba.fmph.asdf.Session;
import sk.uniba.fmph.asdf.db.Account;
import sk.uniba.fmph.asdf.db.AccountManager;
import sk.uniba.fmph.asdf.exceptions.DatabaseException;
import sk.uniba.fmph.asdf.exceptions.PermissionDeniedException;
import sk.uniba.fmph.asdf.gui.views.MainView;
import sk.uniba.fmph.asdf.tempt.PrimaryTestFieldDef;
import sk.uniba.fmph.asdf.tempt.TestTemplate;
import sk.uniba.fmph.asdf.test.RelativePoint;
import sk.uniba.fmph.asdf.test.TestType;
import sk.uniba.fmph.asdf.utils.Convertor;

/**
 * View na vytvaranie serii testov vhodnych na opravu.
 *
 * @author Jakub Gaľ
 */
public class TestCreatorFrame extends Frame implements ActionListener {

	private static final long serialVersionUID = 6050546956573860125L;
	private ArrayList<ArrayList<BufferedImage>> testImagesOrig = new ArrayList<>();
	private ArrayList<ArrayList<Image>> testImages = new ArrayList<>();
	private ArrayList<String> tests = new ArrayList<>();
	private TestTemplate selectedTemplate = null;
	private int selectedTest = -1;
	private Account selectedEvaluator;
	private final String fontFamily = "Verdana";
	// komponenty viewu
	private javax.swing.JButton buttonAddTest;
	private javax.swing.JButton buttonRemoveTest;
	private javax.swing.JButton buttonFinalizeTestSet;
	private javax.swing.JPanel panelCenter;
	private javax.swing.JPanel panelControls;
	private javax.swing.JPanel panelLeft;
	private javax.swing.JPanel panelRight;
	private javax.swing.JPanel panelTestPreview;
	private javax.swing.JScrollPane scrollPaneTemplates;
	private javax.swing.JScrollPane scrollPaneUsers;
	private javax.swing.JScrollPane scrollPaneTests;
	private javax.swing.JScrollPane scrollPaneTestPreview;
	private javax.swing.JTree treeTemplates;
	private javax.swing.JTree treeUsers;
	private javax.swing.JTree treeTests;
	private WebFileChooser fileChooser;

	/**
	 * Vytvorenie viewu a inicializacia jeho komponentov
	 */
	public TestCreatorFrame(MainView parent) {
		super(parent);
		initComponents();
	}

	@Override
	public void onShow() {
		generateTemplateTree();
	}

	/**
	 * Inicializacia komponentov
	 */
	private void initComponents() {

		setMinimumSize(new java.awt.Dimension(800, 600));
		setPreferredSize(new java.awt.Dimension(800, 600));
		setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.LINE_AXIS));

		panelLeft = new javax.swing.JPanel();
		scrollPaneTemplates = new javax.swing.JScrollPane();
		treeTemplates = new javax.swing.JTree();
		scrollPaneUsers = new javax.swing.JScrollPane();
		treeUsers = new javax.swing.JTree();
		panelCenter = new javax.swing.JPanel();
		panelTestPreview = new javax.swing.JPanel() {
			@Override
			public void paint(Graphics g) {
				super.paint(g);

				drawTestPreview(g);
			}
		};
		panelTestPreview.setBackground(Color.white);
		scrollPaneTestPreview = new javax.swing.JScrollPane(
						ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
						ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		panelRight = new javax.swing.JPanel();
		scrollPaneTests = new javax.swing.JScrollPane();
		treeTests = new javax.swing.JTree();
		panelControls = new javax.swing.JPanel();
		buttonAddTest = new javax.swing.JButton();
		buttonRemoveTest = new javax.swing.JButton();
		buttonFinalizeTestSet = new javax.swing.JButton();

		panelLeft.setPreferredSize(new java.awt.Dimension(175, 600));
		panelLeft.setMaximumSize(new java.awt.Dimension(175, Integer.MAX_VALUE));
		panelLeft.setLayout(new java.awt.GridLayout(0, 1));

		generateTemplateTree();
		treeTemplates.addTreeSelectionListener(new TreeSelectionListener() {
			@Override
			public void valueChanged(TreeSelectionEvent tse) {
				changeSelectedTemplate();
			}
		});
		scrollPaneTemplates.setViewportView(treeTemplates);

		panelLeft.add(scrollPaneTemplates);

		generateUserTree();
		treeUsers.addTreeSelectionListener(new TreeSelectionListener() {
			@Override
			public void valueChanged(TreeSelectionEvent tse) {
				DefaultMutableTreeNode node = (DefaultMutableTreeNode) treeUsers.getLastSelectedPathComponent();
				if (node != null && (node.getUserObject() instanceof Account)) {
					selectedEvaluator = (Account) node.getUserObject();
				}
			}
		});
		scrollPaneUsers.setViewportView(treeUsers);

		panelLeft.add(scrollPaneUsers);

		add(panelLeft);

		panelCenter.setBorder(javax.swing.BorderFactory.createEtchedBorder());
		panelCenter.setPreferredSize(new java.awt.Dimension(450, 600));
		panelCenter.setLayout(new java.awt.BorderLayout());

		scrollPaneTestPreview.setViewportView(panelTestPreview);

		panelCenter.add(scrollPaneTestPreview, BorderLayout.CENTER);

		add(panelCenter);

		panelRight.setMaximumSize(new java.awt.Dimension(175, Integer.MAX_VALUE));
		panelRight.setPreferredSize(new java.awt.Dimension(175, 600));
		panelRight.setLayout(new java.awt.GridLayout(0, 1));

		generateTestTree();
		treeTests.addTreeSelectionListener(new TreeSelectionListener() {
			@Override
			public void valueChanged(TreeSelectionEvent tse) {
				changeSelectedTest();
			}
		});
		scrollPaneTests.setViewportView(treeTests);

		panelRight.add(scrollPaneTests);

		panelControls.setMaximumSize(new java.awt.Dimension(175, 150));
		panelControls.setPreferredSize(new java.awt.Dimension(175, 150));
		panelControls.setLayout(new GridLayout(0, 1));

		buttonAddTest.setText("Pridať test");
		buttonAddTest.addActionListener(this);

		buttonRemoveTest.setText("Odobrať test");
		buttonRemoveTest.addActionListener(this);

		buttonFinalizeTestSet.setText("Uložiť sadu testov");
		buttonFinalizeTestSet.addActionListener(this);

		panelControls.add(buttonAddTest);
		panelControls.add(buttonRemoveTest);
		panelControls.add(buttonFinalizeTestSet);

		panelRight.add(panelControls);

		add(panelRight);

		scrollPaneTestPreview.addComponentListener(new ComponentListener() {
			@Override
			public void componentResized(ComponentEvent ce) {
				resizeTestImages();
			}

			@Override
			public void componentMoved(ComponentEvent ce) {
			}

			@Override
			public void componentShown(ComponentEvent ce) {
			}

			@Override
			public void componentHidden(ComponentEvent ce) {
			}
		});
	}

	/**
	 * Spracovanie kliknutia na tlacitka
	 *
	 * @param evt
	 */
	@Override
	public void actionPerformed(java.awt.event.ActionEvent evt) {
		if (evt.getSource() == buttonAddTest) {
			addTest();
		} else if (evt.getSource() == buttonRemoveTest) {
			removeTest();
		} else if (evt.getSource() == buttonFinalizeTestSet) {
			finalizeTestSet();
		}
	}

	/**
	 * Vygenerovanie zoznamu testov
	 */
	private void generateTestTree() {
		// root
		javax.swing.tree.DefaultMutableTreeNode treeNodeRoot = new javax.swing.tree.DefaultMutableTreeNode("Zoznam testov");
		for (String test : tests) {
			javax.swing.tree.DefaultMutableTreeNode treeNodeTest = new javax.swing.tree.DefaultMutableTreeNode(test);
			treeNodeRoot.add(treeNodeTest);
		}
		treeTests.setModel(new javax.swing.tree.DefaultTreeModel(treeNodeRoot));
		treeTests.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
	}

	/**
	 * Vygenerovanie zoznamu sablon
	 */
	private void generateTemplateTree() {
		javax.swing.tree.DefaultMutableTreeNode treeNodeRoot = new javax.swing.tree.DefaultMutableTreeNode("Výber šablóny");

		java.util.HashSet<TestTemplate> templates;
		templates = Session.getAllTemplates();
		for (TestTemplate t : templates) {
			javax.swing.tree.DefaultMutableTreeNode treeNodeTemplate = new javax.swing.tree.DefaultMutableTreeNode(t);
			treeNodeRoot.add(treeNodeTemplate);
		}

		treeTemplates.setModel(new javax.swing.tree.DefaultTreeModel(treeNodeRoot));
		treeTemplates.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
	}

	/**
	 * Vygenerovanie zoznamu pouzivatelov - potencionalnych opravovatelov
	 */
	private void generateUserTree() {
		if (Session.isOnline()) {
            try {
                javax.swing.tree.DefaultMutableTreeNode treeNodeRoot = new javax.swing.tree.DefaultMutableTreeNode("Výber opravovateľa");

                java.util.ArrayList<Account> accounts;
                accounts = AccountManager.getUsers();
                Account myAccount = Session.getAccount();
                for (Account a : accounts) {
                    if (a.getId() == myAccount.getId()) {
                        continue;
                    }
                    javax.swing.tree.DefaultMutableTreeNode treeNodeAccount = new javax.swing.tree.DefaultMutableTreeNode(a);
                    treeNodeRoot.add(treeNodeAccount);

                    treeUsers.setModel(new javax.swing.tree.DefaultTreeModel(treeNodeRoot));
                    treeUsers.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
                }
            } catch (DatabaseException ex) {
            }
        }
	}

	/**
	 * Prida obrazky testu k aktualne zvolenemu testu. Zobrazi FileChooser a po
	 * zvoleni obrazkov ich prida do zoznamu obrazkov aktualneho testu a vytvori
	 * naskalovane kopie na zobrazenie
	 */
	private void addTestPages() {
		if (selectedTest > -1) {
			if (fileChooser == null) {
				fileChooser = new WebFileChooser(parent, "Vyberte strany testu");
				fileChooser.setCurrentDirectory(System.getProperty("user.home"));
				fileChooser.setSelectionMode(SelectionMode.MULTIPLE_SELECTION);
				fileChooser.setAvailableFilter(GlobalConstants.IMAGES_FILTER);
				fileChooser.setChooseFilter(GlobalConstants.IMAGES_FILTER);
			}
			fileChooser.setVisible(true);
			if (fileChooser.getResult() == StyleConstants.OK_OPTION) {
				List<File> imageFiles = fileChooser.getSelectedFiles();
				if (imageFiles.isEmpty()) {
					JOptionPane.showMessageDialog(parent, "Musíte zvoliť aspoň jeden obrázok", "Chyba!", JOptionPane.ERROR_MESSAGE);
				} else {
					for (File f : imageFiles) {
						try {
							Image img = ImageIO.read(f);
							int width = img.getWidth(null);
							int height = img.getHeight(null);
							if (width > 1500) {
								double ratio = (float) 1500 / width;
								width = 1500;
								height = (int) Math.round(height * ratio);
							}

							BufferedImage bimg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
							Graphics2D g = bimg.createGraphics();
							g.drawImage(img.getScaledInstance(width, height, Image.SCALE_SMOOTH), null, null);

							testImagesOrig.get(selectedTest).add(bimg);

							testImages.get(selectedTest).add(null);
						} catch (IOException ex) {
							Logger.getLogger(TemplateEditorFrame.class.getName()).log(Level.SEVERE, null, ex);
						}
					}
					resizeTestImages();
				}
			}
		} else {
			JOptionPane.showMessageDialog(parent, "Musíte zvoliť test ku ktorému chcete pridať strany", "Chyba!", JOptionPane.ERROR_MESSAGE);
		}
	}

	/**
	 * Odobratie aktualne zvoleneho testu zo serie testov
	 */
	private void removeTest() {
		if (selectedTest > -1) {
			tests.remove(selectedTest);
			javax.swing.tree.DefaultTreeModel model = (javax.swing.tree.DefaultTreeModel) treeTests.getModel();
			javax.swing.tree.DefaultMutableTreeNode root = (javax.swing.tree.DefaultMutableTreeNode) model.getRoot();
			javax.swing.tree.DefaultMutableTreeNode node = (javax.swing.tree.DefaultMutableTreeNode) root.getChildAt(selectedTest);
			model.removeNodeFromParent(node);
			treeTests.revalidate();

			selectedTest = -1;

			panelTestPreview.repaint();
			scrollPaneTestPreview.revalidate();
		} else {
			JOptionPane.showMessageDialog(parent, "Musíte zvoliť test na zmazanie", "Chyba!", JOptionPane.ERROR_MESSAGE);
		}
	}

	/**
	 * Vytvorenie testov zo sablony, testovych obrazkov a opravovatelov.
	 * Pouzivatel bude vyzvany na vyber typu testu z dostupnych typov a nasledne
	 * sa kazdy zadany test ulozi a synchronizuje. Obrazky jednotlivych testov
	 * budu naskalovane na jednotnu velkost a spojene do jedneho velkeho obrazku
	 * pre kazdy test.
	 */
	private void finalizeTestSet() {
		if (selectedTemplate == null) {
			JOptionPane.showMessageDialog(parent, "Musíte zvoliť šablónu", "Chyba!", JOptionPane.ERROR_MESSAGE);
		} else if (selectedEvaluator == null) {
			JOptionPane.showMessageDialog(parent, "Musíte zvolit prideleného opravovateľa", "Chyba!", JOptionPane.ERROR_MESSAGE);
		} else if (tests.isEmpty()) {
			JOptionPane.showMessageDialog(parent, "Musíte pridať aspoň jeden test", "Chyba!", JOptionPane.ERROR_MESSAGE);
		} else {
			Object[] options = {TestType.REGULAR, TestType.PRACTICE, TestType.QUALIFYING, TestType.SEED};
			final TestType testType = (TestType) JOptionPane.showInputDialog(
							parent,
							"Vyber typ testu",
							"Typ testu",
							JOptionPane.QUESTION_MESSAGE,
							null,
							options,
							TestType.REGULAR);

			if (testType != null) {
				new Thread(new Runnable() {
					@Override
					public void run() {
						int width = 1500;

						for (int i = 0; i < tests.size(); i++) {
							String studentName = tests.get(i);
							ArrayList<BufferedImage> images = testImagesOrig.get(i);

							int height = 0;
							for (BufferedImage im : images) {
								height += Math.round(((float) width / im.getWidth(null)) * im.getHeight(null));
							}

							BufferedImage testImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
							Graphics2D g = testImage.createGraphics();

							int ty = 0;
							for (BufferedImage im : images) {
								Image scaled = im.getScaledInstance(width, -1, Image.SCALE_SMOOTH);
								g.drawImage(scaled, 0, ty, null);
								ty += scaled.getHeight(null);
							}

							try {
								Session.createTest(testType, studentName, selectedEvaluator, selectedTemplate, testImage);
							} catch (PermissionDeniedException e) {
								JOptionPane.showMessageDialog(parent, "Nedostatočné práva:\n" + e.getMessage(), "Chyba", JOptionPane.ERROR_MESSAGE);
								return;
							}
						}

						if (parent.synchronizeLocalFiles() && (!Session.isOnline() || parent.synchronizeWithDB())) {
							JOptionPane.showMessageDialog(parent, "Testy boli úspešne vytvorené", "Testy vytvorené", JOptionPane.INFORMATION_MESSAGE);
						}
					}
				}).start();
				parent.clearStatusLabel();
			}
		}
	}

	/**
	 * Vytvorenie kopii originalnych obrazkov aktualne zvoleneho testu a ich
	 * naskalovanie na velkost okna.
	 */
	private void resizeTestImages() {
		if (selectedTest > -1) {
            Session.addStatusMessage("Škálujem obrázky...");
//			parent.setStatusLabel("Škálujem obrázky...", MainView.WAIT_ICON);
			new Thread(new Runnable() {
				@Override
				public void run() {

					int height = 0;
					int width = scrollPaneTestPreview.getWidth() - scrollPaneTestPreview.getVerticalScrollBar().getWidth();

					for (int i = 0; i < testImagesOrig.get(selectedTest).size(); i++) {
						Image im = testImagesOrig.get(selectedTest).get(i).getScaledInstance(width, -1, Image.SCALE_SMOOTH);
						testImages.get(selectedTest).set(i, im);

						height += im.getHeight(null);
					}
					/**/
					panelTestPreview.setPreferredSize(new Dimension(width, height));
					/**/
					panelTestPreview.revalidate();
					panelTestPreview.repaint();
					scrollPaneTestPreview.revalidate();
					scrollPaneTestPreview.repaint();

					int scrollAmount = Math.round(height / scrollPaneTestPreview.getHeight() * 20);
				}
			}).start();
			parent.clearStatusLabel();
		}
	}

	/**
	 * Vykreslenie nahladu testu tak ako bude vyzerat pri oprave. Skombinuju sa
	 * podkladove obrazky testu a sablona.
	 *
	 * @param g - graficka plocha do ktorej vykreslujeme
	 */
	private void drawTestPreview(Graphics g) {
		Graphics2D g2 = (Graphics2D) g;
		g2.setStroke(new BasicStroke(2));
		Font font = new Font(fontFamily, Font.BOLD, 24);
		g2.setFont(font);
		FontRenderContext frc = new FontRenderContext(null, true, false);

		if (selectedTest > -1) {
			int ty = 0;
			for (Image i : testImages.get(selectedTest)) {
				g2.drawImage(i, 0, ty, null);
				ty += i.getHeight(null);
			}

			if (selectedTemplate != null) {
				for (PrimaryTestFieldDef f : selectedTemplate.getFieldDefs()) {
					RelativePoint tl = f.getTopLeft();
					RelativePoint br = f.getBottomRight();
					Dimension d = panelTestPreview.getSize();

					Point p1 = Convertor.relToAbs(tl, d);
					Point p2 = Convertor.relToAbs(br, d);
					int w = p2.x - p1.x;
					int h = p2.y - p1.y;

					g2.setColor(new Color(0, 255, 0, 50));
					g2.fillRect(p1.x, p1.y, w, h);
					g2.setColor(Color.green);
					g2.drawRect(p1.x, p1.y, w, h);
					g2.setColor(Color.black);

					String points = String.valueOf(f.getMaxPoints());
					Rectangle2D bounds = font.getStringBounds(points, frc);

					float textX = p1.x + (w / 2 - (float) bounds.getCenterX());
					float textY = p1.y + (h / 2 - (float) bounds.getCenterY());

					g2.drawString(points, textX, textY);
				}
			}
		}
	}

	/**
	 * Nastavi aktualne zvolenu sablonu podla vyberu v zozname sablon.
	 */
	private void changeSelectedTemplate() {
		DefaultMutableTreeNode node = (DefaultMutableTreeNode) treeTemplates.getLastSelectedPathComponent();
		if (node != null && (node.getUserObject() instanceof TestTemplate)) {
			selectedTemplate = (TestTemplate) node.getUserObject();

			panelTestPreview.repaint();
		}
	}

	/**
	 * Prida test zo zadanym menom do aktualnej zbierky testov.
	 */
	private void addTest() {
		String name = null;

		boolean validName = false;
		while (!validName) {
			validName = true;
			name = JOptionPane.showInputDialog(super.parent, "Zadajte meno autora testu (Meno Priezvisko) ");

			if (name == null) {
				return;
			}

			if (name.equals("")) {
				validName = false;
				JOptionPane.showMessageDialog(parent, "Meno autora je povinné", "Chyba!", JOptionPane.ERROR_MESSAGE);
			} else {
				Pattern namePattern = Pattern.compile("^[A-Za-zľščťžýáíéúäôňňĺ]+\\s[A-Za-zľščťžýáíéúäôňňĺ]+$");
				if (!namePattern.matcher(name).matches()) {
					validName = false;
					JOptionPane.showMessageDialog(parent, "Meno autora nemá správny formát (Meno Priezvisko)", "Chyba!", JOptionPane.ERROR_MESSAGE);
				}
			}
		}

		tests.add(name);
		int index = tests.indexOf(name);

		selectedTest = index;
		testImagesOrig.add(index, new ArrayList<BufferedImage>());
		testImages.add(index, new ArrayList<Image>());

		addTestPages();

		generateTestTree();
		treeTests.setSelectionRow(index + 1);
	}

	/**
	 * Zmeni aktualne zvoleny test podla vyberu v zozname testov.
	 */
	private void changeSelectedTest() {
		DefaultMutableTreeNode node = (DefaultMutableTreeNode) treeTests.getLastSelectedPathComponent();
		if (node != null && (node.getUserObject() instanceof String)) {
			String value = (String) node.getUserObject();
			selectedTest = tests.indexOf(value);

			resizeTestImages();
		}
	}
}
